from utils import *
import user
import datetime
import database
import account
import hashlib
import json
import friends
import signature
import net
import threading

INFO_ID_CONTACT = "me"
INFO_ID_GROUPS = "groups"
INFO_ID_APPS = "apps"

class informationError(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return repr(self.value)

class Information:
    """defines an information token in myworld"""
    data = ""
    ID = ""
    date = None
    author = None
    fr = []
    signature = None
    
    def __init__(self,ID,author,date,fr,data,signature):
        """ Create an info object
        date should be a datetime object, author is an account object, fr is a friend group object, signature is a signature object"""
        self._data = data
        self._ID = ID
        self._date = date
        self._author = author
        self._fr = fr
        self._signature = signature
        
    def getID(self):
        """return the ID of the information (string)"""
        return self._ID
    
    def getAuthor(self):
        """output an account object"""
        return self._author
    
    def getDate(self):
        """return a datetime object"""
        return self._date
    
    def getFr(self):
        """return a friend group object"""
        return self._fr
    
    def getSignature(self):
        """return a signature object"""
        return self._signature
    
    def setSignature(self,signData):
        """signature should be a signature object"""
        self._signature = signData
    
    def getData(self):
        return self._data
    
    def getSerializeNoSign(self):
        #serialize fr
        frs = ""
        for f in self.getFr():
            frs += f.getID() + ','
        return str("%s;%s;%s;%s;%s"%(self.getID(),self.getAuthor().getID(),self.getDate().isoformat(),frs,self.getData()))
    
    def checkSignature(self):
        """return True if signature OK"""
        infoOutput("CHECK SIGN information (%s)"%self.getSerializeNoSign())
        digest = hashlib.sha1(self.getSerializeNoSign()).digest()
        sign = self.getSignature().getValue()
        return self.getAuthor().getKey().verify(digest,sign)
    
    def dumps(self):
        """func that create a JSON string from information data"""
        dicForJson = {}
        dicForJson["ID"] = self.getID()
        dicForJson["Author"] = self.getAuthor().getID()
        dicForJson["Date"] = self.getDate().isoformat()
        dicForJson["FR"] = []
        for f in self.getFr():
            dicForJson["FR"].append(f.getID())
        dicForJson["Data"] = self.getData()
        dicForJson["Sign"] = self.getSignature().dumps()
        return json.dumps(dicForJson)
    
    def exportToFile(self,filePath):
        with open(filePath,'wb') as fic:
            fic.write(self.dumps())
        return
    
    def __cmp__(self,other):
        if isinstance(other,Information):
            if self.getDate() > other.getDate():
                return 1
            elif self.getDate() == other.getDate():
                return 0
            else:
                return -1
        else:
            raise informationError("Can't compare information object with %s object."%type(other))
    
def loadInformation(jsonString,checkSig=True):
    """func that loads information object from a json string and check sign if needed
    checkSig : if signature of information should be check"""
    dicJson = json.loads(jsonString)
    id = dicJson["ID"]
    author = friends.getGlobalAccount(dicJson["Author"])
    date = datetime.datetime.strptime(dicJson["Date"],"%Y-%m-%dT%H:%M:%S.%f")
    fr = friends.friendsGroup([])
    for f in dicJson["FR"]:
        fr.append(friends.getGlobalAccount(f))
    data = dicJson["Data"]
    sign = signature.signature()
    sign.loads(dicJson["Sign"])
    info = Information(id,author,date,fr,data,sign)
    if (not checkSig) or info.checkSignature():
        return info
    else:
        warningOutput("Bad information signature (ID:%s,Author:%s)"%(id,author.getID()))
        raise informationError("Bad information signature (ID:%s,Author:%s)"%(id,author.getID()))
    
def loadInformationFromFile(filePath,checkSig=True):
    """func that loads information object from a file
    checkSig : if signature of information should be check"""
    with open(filePath,'rb') as fic:
        jsonInfo = fic.read()
    return loadInformation(jsonInfo,checkSig)
    
def getInformation(ID,author,callback=None,fr=None):
    """ get information object from the database and on network
    ID is string IDs, author is account object, fr is friendgroup object
    callback is a function callback when the newest version is found
        should be called : callback(infoObj)
    return info object or None if not found in base
    launch callback when found on network"""
    base = database.getMyDatabase()
    infoObj = None
    if base.isExistInformation(ID,author.getID()):
        infoObj = base.getInformation(ID,author.getID())
    if infoObj:
        infoDate = infoObj.getDate()
    else:
        infoDate = None
    #launch net grab process except if author is current account
    if author != account.getMyAccount():
        grabThread = threading.Thread(group=None, target=net.mainGrabProcess, name=None, args=(ID,author,fr,infoDate,callback))
        grabThread.setDaemon(True)
        grabThread.start()
    return infoObj
    
def setInformation(info):
    """ set information to the database 
    check the version"""
    base = database.getMyDatabase()
    if base.isExistInformation(info.getID(),info.getAuthor().getID()):
        #if info exists, check if newer
        inbaseinfo = base.getInformation(info.getID(),info.getAuthor().getID())
        if info > inbaseinfo :
            return base.updateInformation(info)
        else:
            infoOutput("Information %s is older than in database"%(info.getID(),))
    else:
        return base.addInformation(info)
    
def getPersonalInformation(ID):
    """ get personal information (local account) from the database """
    my = account.getMyAccount()
    return getInformation(ID,my)
    
def setPersonalInformation(ID,fr,data):
    """ set personal information (local account) to the database """
    base = database.getMyDatabase()
    my = account.getMyAccount()
    info = Information(ID,my,datetime.datetime.now(),fr,data,None)
    info.setSignature(my.signInformation(info))
    if base.isExistInformation(info.getID(),info.getAuthor().getID()):
        return base.updateInformation(info)
    else:
        return base.addInformation(info)
    
def searchInformations(critID='%',critAuthorID='%'):
    """search informations in base with crit specified
    return list of information objects"""
    base = database.getMyDatabase()
    #make the search in db
    result = base.searchInformations(critID,critAuthorID)
    #convert result to list of informations
    infoResult = []
    for row in result:
        infoResult.append(getInformation(row[0],row[1]))
    return infoResult
